
import numpy as np

# FIXME: 这里的趋势延续，如果最开始是下降，但会随着k线增加变为上升趋势，而条件被一直满足，这需要解决
def find_inflection_points(y_list, test=1):
    inflection_points = [0]
    slope_list = []
    n = len(y_list)
    start_idx = 0
    if test == 1: # 测试以往
        weight = 1
    else: # 预测未来
        weight = 2

    cnt = 3
    arg1 = 2 * weight # 单数据偏离arg
    arg2 = 2 * weight # 总计偏离arg
    arg3 = 10 * weight # 合并同趋势斜率arg
    arg4 = 1 * weight # 合并异趋势斜率arg
    arg5 = 2 # 同趋势趋势斜率最小值arg
    arg6 = 2 # 异趋势趋势斜率最小值arg
    arg7 = 2 # 趋势与初始时最大修改限度arg
    changes = np.diff(y_list)/y_list[:-1]
    volatility = np.mean(np.abs(changes))
    slope_min = np.abs(volatility) # 趋势斜率最小值
    
    # 开始轮询point
    while start_idx < n - 1:
        end_idx = start_idx + 1
        last_valid_end = start_idx

        # 对趋势刚开始时使用前5天数值计算动态阈值，对于最开始数据，使用全部数据计算动态阈值
        if start_idx < 5:
            changes = np.diff(y_list) / y_list[:-1]
        else:
            changes = np.diff(y_list[start_idx-5:start_idx+1]) / y_list[start_idx-5:start_idx+1][:-1]
        volatility = np.mean(np.abs(changes))
        max_deviation = volatility * arg1
        threshold = volatility * np.sqrt(cnt) * arg2
        max_deviation_temp = max_deviation
        threshold_temp = threshold

        # 开始寻找趋势
        while end_idx < n:
            x0, y0 = start_idx, y_list[start_idx]
            xn, yn = end_idx, y_list[end_idx]
            
            # 计算斜率
            slope = (yn - y0) / (xn - x0)
            deviations = []
            for i in range(start_idx+1, end_idx):
                xi = i
                yi = y_list[i]
                y_pred = y0 + slope * (xi - x0)
                deviations.append((yi - y_pred) / y_pred)

            # 连续cnt个点后开始统计并使用该趋势内的平均偏差
            if(end_idx - start_idx > cnt): # 判断连续多少个点后开始统计并使用该趋势平均偏差，超过平均偏差weight倍则认为是趋势结束
                changes = np.diff(y_list[start_idx:end_idx]) / y_list[start_idx:end_idx][:-1]
                volatility = np.mean(np.abs(changes))
                max_deviation_temp = volatility * arg1
                threshold_temp = volatility * np.sqrt(len(y_list[start_idx:end_idx])) * arg2

            # 判断单数据偏离阈值百分比
            if(not deviations or np.abs(deviations[-1]) <= max_deviation_temp): # 判断单数据偏离阈值百分比
                if(not deviations or abs(np.sum(deviations)) < threshold_temp): # 判断总计偏离阈值百分比
                    last_valid_end = end_idx
                    end_idx += 1
                    continue
                else:
                    # print("和数据偏离阈值百分比", threshold_temp, np.mean(deviations))
                    pass
            else:
                # print("单数据偏离阈值百分比", max_deviation_temp, deviations)
                pass
            break
                
        inflection_points.append(last_valid_end)
        slope_list.append((y_list[last_valid_end] - y_list[start_idx])/(last_valid_end - start_idx))
        start_idx = last_valid_end
    if test == 1: # 测试以往需要合并相似斜率
        # 计算斜率并合并相似斜率
        mergelist = []
        if len(slope_list) > 1:
            slope_init = slope_list[0]
            for i in range(len(slope_list)-1):
                if (slope_list[i] > 0 and slope_list[i+1] < 0) or (slope_list[i] < 0 and slope_list[i+1] > 0):
                    slope_th = max(abs(slope_list[i]), slope_min*arg6) * arg4
                    # print("异趋势", abs(slope_list[i]), slope_min*arg6)
                else:
                    slope_th = max(abs(slope_list[i]), slope_min*arg5) * arg3
                    # print("同趋势", abs(slope_list[i]), slope_min*arg5)
                slope_new = (y_list[inflection_points[i+2]] - y_list[inflection_points[i]]) / (inflection_points[i+2] - inflection_points[i])
                if (abs(slope_list[i] - slope_list[i+1]) < slope_th) and (np.abs(slope_new - slope_init) < (slope_th * arg7)):
                    slope_list[i+1] = slope_new
                    mergelist.append(inflection_points[i+1])
                    # print("合并")
                else:
                    slope_init = slope_list[i+1]
                    # print("不合并")

        inflection_points = [x for x in inflection_points if x not in mergelist]
    
    return inflection_points

def trend_analysis(y_list, test=1):
    inflection_points = find_inflection_points(y_list, test=test)
    return inflection_points

def trend_point(dataframe):
    arg1 = 0.2 # 横盘趋势阈值arg
    arg2 = 2 # 横盘趋势涨幅最小值arg
    arg3 = 2 # 趋势反转
    arg4 = 1 # 趋势加速arg
    arg5 = 1 # 趋势变缓arg

    inflection_points = trend_analysis(dataframe['close'].tolist(), test=0)
    y_list = dataframe['close'].tolist()
    # 买入前，应为横盘或下降趋势，找到最大横盘斜率
    changes = np.diff(y_list)/y_list[:-1]
    abs_changes = np.abs(changes)
    q1, q3 = np.percentile(abs_changes, [25, 75])
    iqr = q3 - q1
    lower = q1 - 1.5 * iqr
    upper = q3 + 1.5 * iqr
    # 限制范围在0~最大值之间，避免负下限
    filtered = abs_changes[(abs_changes >= max(lower, 0)) & (abs_changes <= upper)]
    volatility = np.mean(filtered) if len(filtered) > 0 else 0
    slope_min = np.abs(volatility) * arg1 # 趋势斜率最小值
    increase_rate1 = volatility * arg2 # 横盘
    increase_rate2 = volatility * arg3 # 趋势反转
    increase_rate3 = volatility * arg4 # 趋势加速
    increase_rate4 = volatility * arg5 # 趋势变缓
    
    slope_list = []
    buy_point = []
    sell_point = []
    for i in range(len(inflection_points)-1):
        slope_list.append(((y_list[inflection_points[i+1]] - y_list[inflection_points[i]]) / y_list[inflection_points[i]]) / (inflection_points[i+1] - inflection_points[i]))
    # print(inflection_points)
    # print(slope_list)
    # print(slope_min)

    for i in range(len(slope_list)-1):
        increase = (y_list[inflection_points[i+1]+1] - y_list[inflection_points[i+1]]) / y_list[inflection_points[i+1]]
        # print(increase, increase_rate1)
        
        # 横盘突破
        if np.abs(slope_list[i]) < slope_min and increase > increase_rate1:
            buy_point.append(("横盘突破", inflection_points[i+1]))

        # 横盘破位
        if np.abs(slope_list[i]) < slope_min and increase < -increase_rate1:
            sell_point.append(("横盘破位", inflection_points[i+1]))
        
        # 下降趋势
        if slope_list[i] < -slope_min:
            rate = np.sqrt((abs(slope_list[i]) - slope_min) / slope_min)
            if (increase > (increase_rate2 * rate)):
                buy_point.append(("下降趋势反转", inflection_points[i+1]))
            elif (increase > (increase_rate4)):
                buy_point.append(("下降趋势变缓", inflection_points[i+1]))
            elif (increase < -(increase_rate3 * rate)):
                sell_point.append(("下降趋势加速", inflection_points[i+1]))
        
        # 上升趋势加速
        if slope_list[i] > slope_min:
            rate = np.sqrt((abs(slope_list[i]) - slope_min) / slope_min)
            if (increase > (increase_rate3 * rate)):
                buy_point.append(("上升趋势加速", inflection_points[i+1]))
            elif (increase < -(increase_rate2 * rate)):
                sell_point.append(("上升趋势反转", inflection_points[i+1]))
            elif (increase < -(increase_rate4)):
                sell_point.append(("上升趋势变缓", inflection_points[i+1]))
    return buy_point, sell_point

def trend_buy_point(dataframe):
    return trend_point(dataframe)[0]

def trend_sell_point(dataframe):
    return trend_point(dataframe)[1]

def trend_buy_text():
    return ["横盘突破", "下降趋势反转", "下降趋势变缓", "上升趋势加速"]

def trend_sell_text():
    return ["横盘破位", "上升趋势反转", "上升趋势变缓", "下降趋势加速"]

def trend_is_bottom(y_list, threadhold=0.1):
    last_close = y_list[-1]
    n = len(y_list)
    count_smaller = sum(1 for x in y_list[-n:] if x < last_close)
    if count_smaller / n < threadhold:
        return True

def trend_is_top(y_list, threadhold=0.1):
    last_close = y_list[-1]
    n = len(y_list)
    count_greater = sum(1 for x in y_list[-n:] if x > last_close)
    if count_greater / n < threadhold:
        return True

def trend_is_sideways(y_list):
    changes = np.diff(y_list)/y_list[:-1]
    volatility = np.mean(np.abs(changes))
    slope_min = np.abs(volatility) # 趋势斜率最小值
    inflection_points = find_inflection_points(y_list, test=1)
    y = (y_list[inflection_points[-1]] - y_list[inflection_points[-2]])
    x = (inflection_points[-1] - inflection_points[-2])
    if x > (0.1 * len(y_list)):
        if ((y/x < 3*slope_min) and (y/x > -slope_min)):
            return True
    return False

def trend_value_is_increase(df): # 是否是有效大阳线
    # 加分：实体大/涨幅大/上影线短
    score = 0
    y_list = df['close'].tolist()
    changes = np.diff(y_list)/y_list[:-1]
    volatility = np.mean(np.abs(changes)) # 平均波动率
    threadhold = 3 * volatility # 波动率阈值
    if (df['close'].iloc[-1] - df['close'].iloc[-2]) / df['close'].iloc[-2] > threadhold: # 涨幅大
        score += 1
    if (df['close'].iloc[-1] - df['open'].iloc[-1]) / df['open'].iloc[-1] > threadhold: # 实体大
        score += 1
    if (df['high'].iloc[-1] - df['close'].iloc[-1]) / (df['close'].iloc[-1] - df['close'].iloc[-2]) < (1/3): # 上影线为涨幅的1/3
        score += 1
    if score >= 2:
        return True
    return False

def trend_value_is_decrease(df): # 是否是有效大阴线
    # 加分：实体大/跌幅大/下影线短
    score = 0
    y_list = df['close'].tolist()
    changes = np.diff(y_list)/y_list[:-1]
    volatility = np.mean(np.abs(changes)) # 平均波动率
    threadhold = 3 * volatility # 波动率阈值
    if (df['close'].iloc[-1] - df['close'].iloc[-2]) / df['close'].iloc[-2] < -threadhold: # 跌幅大
        score += 1
    if (df['close'].iloc[-1] - df['open'].iloc[-1]) / df['open'].iloc[-1] < -threadhold: # 实体大
        score += 1
    if (df['low'].iloc[-1] - df['close'].iloc[-1]) / (df['close'].iloc[-1] - df['close'].iloc[-2]) < (1/3): # 下影线为跌幅的1/3
        score += 1
    if score >= 2:
        return True
    return False

